﻿using Aliyun.OSS;
using Aliyun.OSS.Common;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;

namespace Storage.Aliyun
{
    /// <summary>
    /// 阿里云Oss
    /// 更多支持参考：https://www.cnblogs.com/SavionZhang/p/13755477.html
    /// </summary>
    public class StorageAliyun
    {
        OssClient _client;
        public StorageAliyun()
        {
            // 创建OssClient实例。
            _client = new OssClient("https://oss-cn-shenzhen.aliyuncs.com", "appkey", "secret");
        }

        /// <summary>
        /// 每片大小
        /// </summary>
        public int PartSize => 10 * 1024 * 1024;

        /// <summary>
        /// 最大片数
        /// </summary>
        public int MaxPart => 9999;

        /// <summary>
        /// 进度
        /// </summary>
        public Action<int> Progress => null;

        /// <summary>
        /// 根据位置大小获取文件流
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        /// <returns></returns>
        public byte[] GetStreamData(string bucketName, string objectName, long offset, long count)
        {
            if (_client == null)
                return null;
            GetObjectRequest request = new GetObjectRequest(bucketName, objectName);
            if (Progress != null)
                request.StreamTransferProgress += streamProgressCallback;
            request.SetRange(offset, offset + count - 1);
            string requestId = string.Empty;
            try
            {
                int len = 0;
                byte[] data = new byte[count];
                using (var response = _client.GetObject(request))
                {
                    requestId = response.RequestId;
                    int tmp = 0;
                    while ((tmp = response.ResponseStream.Read(data, len, data.Length - len)) > (int)0)
                    {
                        len += tmp;
                    }
                }
                return data;
            }
            catch (Exception ex)
            {
                throw new Exception($"GetStreamData error :{ex.Message}，RequestId：{requestId}");
            }
        }
        
        /// <summary>
        /// 进度
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void streamProgressCallback(object sender, StreamTransferProgressArgs args)
        {
            //System.Console.WriteLine("ProgressCallback - Progress: {0}%, TotalBytes:{1}, TransferredBytes:{2} ",
            //    args.TransferredBytes * 100 / args.TotalBytes, args.TotalBytes, args.TransferredBytes);
            Progress?.Invoke((int)(args.TransferredBytes * 100 / args.TotalBytes));
        }

        /// <summary>
        /// 获取文件大小
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public Int64 GetContentLength(string bucketName, string objectName)
        {
            if (_client == null)
                return -1;
            OssObject ossObject = null;
            string requestId = string.Empty;
            try
            {
                ossObject = _client.GetObject(bucketName, objectName);
                requestId = ossObject.RequestId;
                Int64 length = ossObject.Metadata.ContentLength;
                ossObject.Dispose();
                return length;
            }
            catch (Exception e)
            {
                ossObject?.Dispose();
                Console.WriteLine(e);
                throw new Exception($"GetContentLength error :{e.Message}，RequestId：{requestId}");
            }
        }

        /// <summary>
        /// 文件是否存在
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public bool DoesObjectExist(string bucketName, string objectName)
        {
            if (_client == null)
                return false;
            try
            {
                Int64 len = GetContentLength(bucketName, objectName);
                // 判断文件是否存在。
                return _client.DoesObjectExist(bucketName, objectName);
            }
            catch (OssException ex)
            {
                new Exception($"Failed with error code: {ex.ErrorCode}; Error info: {ex.Message}. \nRequestID:{ex.RequestId}\tHostID:{ex.HostId}");
            }
            catch (Exception ex)
            {
                new Exception($"Failed with error info: { ex.Message}");
            }
            return false;
        }

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <returns></returns>
        public byte[] DownLoadObject(string bucketName, string objectName)
        {
            if (_client == null)
                return null;
            try
            {
                Int64 len = GetContentLength(bucketName, objectName);
                return GetStreamData(bucketName, objectName, 0, len);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 断点续传下载
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <param name="checkPointDir"></param>
        public void ResumDownLoadObject(string bucketName, string objectName, string localFileName, string checkPointDir)
        {
            string requestId = string.Empty;
            try
            {
                // 通过DownloadObjectRequest设置多个参数。
                DownloadObjectRequest request = new DownloadObjectRequest(bucketName, objectName, localFileName)
                {
                    // 指定下载的分片大小。
                    PartSize = PartSize,
                    // 指定并发线程数。
                    ParallelThreadCount = 3,
                    // checkpointDir用于保存断点续传进度信息。如果某一分片下载失败，再次下载时会根据文件中记录的点继续下载。如果checkpointDir为null，断点续传功能不会生效，每次失败后都会重新下载。
                    CheckpointDir = checkPointDir,
                };
                request.StreamTransferProgress += streamProgressCallback;
                // 断点续传下载。
                var result = _client.ResumableDownloadObject(request);
            }
            catch (Exception ex)
            {
                throw new Exception($"ResumDownLoadObject error :{ex.Message}，RequestId：{requestId}");
            }
        }

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="bucketName">bucket</param>
        /// <param name="objectName">objectName</param>
        /// <param name="localFileName">本地文件地址</param>
        /// <returns></returns>
        public bool UploadObject(string bucketName, string objectName, string localFileName)
        {
            if (_client == null | !File.Exists(localFileName))
                return false;
            string requestId = string.Empty;
            try
            {
                PutObjectResult resp = _client.PutObject(bucketName, objectName, localFileName);
                requestId = resp.RequestId;
                return resp?.HttpStatusCode == HttpStatusCode.OK;
            }
            catch (Exception ex)
            {
                throw new Exception($"UploadObject error :{ex.Message}，RequestId：{requestId}");
            }
        }

        /// <summary>
        /// 分片上传
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <returns></returns>
        public bool MultipartUpload(string bucketName, string objectName, string localFileName)
        {
            bool bRst = true;
            // 初始化分片上传。
            var uploadId = "";
            string requestId = string.Empty;
            try
            {
                // 定义上传的文件及所属存储空间的名称。您可以在InitiateMultipartUploadRequest中设置ObjectMeta，但不必指定其中的ContentLength。
                var request = new InitiateMultipartUploadRequest(bucketName, objectName);
                var result = _client.InitiateMultipartUpload(request);
                requestId = result.RequestId;
                uploadId = result.UploadId;
                // 打印UploadId。
                Console.WriteLine("Init multi part upload succeeded");
                Console.WriteLine("Upload Id:{0}", result.UploadId);
            }
            catch (Exception ex)
            {
                throw new Exception($"Init multi part upload failed, {ex.Message}");
                Console.WriteLine("Init multi part upload failed, {0}", ex.Message);
            }
            // 计算分片总数。
            long partSize = PartSize;
            var fi = new FileInfo(localFileName);
            var fileSize = fi.Length;
            var partCount = fileSize / partSize;
            if (partCount > MaxPart)
            {
                partSize = fileSize / MaxPart;
                partCount = MaxPart;
            }
            if (fileSize % partSize != 0)
            {
                partCount++;
            }
            // 开始分片上传。PartETags是保存PartETag的列表，OSS收到用户提交的分片列表后，会逐一验证每个分片数据的有效性。当所有的数据分片通过验证后，OSS会将这些分片组合成一个完整的文件。
            var partETags = new List<PartETag>();
            try
            {
                using (var fs = File.Open(localFileName, FileMode.Open))
                {
                    for (var i = 0; i < partCount; i++)
                    {
                        var skipBytes = (long)partSize * i;
                        // 定位到本次上传的起始位置。
                        fs.Seek(skipBytes, 0);
                        // 计算本次上传的分片大小，最后一片为剩余的数据大小。
                        var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
                        var request = new UploadPartRequest(bucketName, objectName, uploadId)
                        {
                            InputStream = fs,
                            PartSize = size,
                            PartNumber = i + 1
                        };
                        // 调用UploadPart接口执行上传功能，返回结果中包含了这个数据片的ETag值。
                        var result = _client.UploadPart(request);
                        partETags.Add(result.PartETag);

                        //传递进度
                        Progress?.Invoke((int)(1.0F * (i + 1) / partCount * 100));
                        Console.WriteLine("finish {0}/{1}", partETags.Count, partCount);
                    }
                    Console.WriteLine("Put multi part upload succeeded");
                }
            }
            catch (Exception ex)
            {
                bRst = false;
                throw new Exception($"Put multi part upload failed, {ex.Message}，RequestId：{requestId}");
                Console.WriteLine("Put multi part upload failed, {0}", ex.Message);
            }
            // 完成分片上传。
            try
            {
                var completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId);
                foreach (var partETag in partETags)
                {
                    completeMultipartUploadRequest.PartETags.Add(partETag);
                }
                var result = _client.CompleteMultipartUpload(completeMultipartUploadRequest);
                Console.WriteLine("complete multi part succeeded");
            }
            catch (Exception ex)
            {
                bRst = false;
                throw new Exception($"complete multi part failed, {ex.Message}，RequestId：{requestId}");
            }
            return bRst;
        }

        /// <summary>
        /// 断点续传上传
        /// </summary>
        /// <param name="bucketName"></param>
        /// <param name="objectName"></param>
        /// <param name="localFileName"></param>
        /// <param name="uploadId"></param>
        /// <returns></returns>
        public bool ResumableUploadObject(string bucketName, string objectName, string localFileName, ref string requestId)
        {
            bool bRst = false;
            if (string.IsNullOrWhiteSpace(requestId))
                bRst = ResumableUpload(bucketName, objectName, localFileName, ref requestId);
            else
                bRst = ResumableMultiPart(bucketName, objectName, localFileName, ref requestId);
            return bRst;
        }

        /// <summary>
        /// 断点分片续传
        /// </summary>
        /// <returns></returns>
        bool ResumableMultiPart(string bucketName, string objectName, string localFileName, ref string requestId)
        {
            // 开始分片上传。PartETags是保存PartETag的列表，OSS收到用户提交的分片列表后，会逐一验证每个分片数据的有效性。当所有的数据分片通过验证后，OSS会将这些分片组合成一个完整的文件。
            var partETags = new List<PartETag>();
            try
            {
                var parts = _client.ListParts(new ListPartsRequest(bucketName, objectName, requestId));
                // 计算分片总数。
                long partSize = PartSize;
                var fi = new FileInfo(localFileName);
                var fileSize = fi.Length;
                var partCount = fileSize / partSize;
                if (partCount > MaxPart - 1)
                {
                    partSize = fileSize / (MaxPart - 1);
                    partCount = MaxPart - 1;
                }
                if (fileSize % partSize != 0)
                {
                    partCount++;
                }
                if (parts.Parts.Any())
                    partETags.AddRange(parts.Parts.Select(p => p.PartETag));
                using (var fs = File.Open(localFileName, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    for (var i = 0; i < partCount; i++)
                    {
                        if (i < parts.Parts.Last()?.PartNumber)
                            continue;
                        var skipBytes = (long)partSize * i;
                        // 定位到本次上传的起始位置。
                        fs.Seek(skipBytes, 0);
                        // 计算本次上传的分片大小，最后一片为剩余的数据大小。
                        var size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
                        var request = new UploadPartRequest(bucketName, objectName, requestId)
                        {
                            InputStream = fs,
                            PartSize = size,
                            PartNumber = i + 1
                        };
                        // 调用UploadPart接口执行上传功能，返回结果中包含了这个数据片的ETag值。
                        var result = _client.UploadPart(request);
                        partETags.Add(result.PartETag);

                        //传递进度
                        Progress?.Invoke((int)(1.0F * (i + 1) / partCount * 100));
                        Console.WriteLine("finish {0}/{1}", partETags.Count, partCount);
                    }
                    Console.WriteLine("Put multi part upload succeeded");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Put multi part upload failed, {0}", ex.Message);
                return false;
            }
            // 完成分片上传。
            try
            {
                var completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, requestId);
                foreach (var partETag in partETags)
                {
                    completeMultipartUploadRequest.PartETags.Add(partETag);
                }
                var result = _client.CompleteMultipartUpload(completeMultipartUploadRequest);
                Console.WriteLine("complete multi part succeeded");
            }
            catch (Exception ex)
            {
                Console.WriteLine("complete multi part failed, {0}", ex.Message);
                return false;
            }
            return true;
        }

        /// <summary>
        /// 断点继续上传
        /// </summary>
        /// <returns></returns>
        bool ResumableUpload(string bucketName, string objectName, string localFileName, ref string requestId)
        {
            try
            {
                string cacheDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SqrayCache");
                if (!Directory.Exists(cacheDir))
                    Directory.CreateDirectory(cacheDir);
                // 通过UploadFileRequest设置多个参数。
                UploadObjectRequest request = new UploadObjectRequest(bucketName, objectName, localFileName)
                {
                    // 指定上传的分片大小。
                    PartSize = PartSize,
                    // 指定并发线程数。
                    ParallelThreadCount = 3,
                    // checkpointDir保存断点续传的中间状态，用于失败后继续上传。如果checkpointDir为null，断点续传功能不会生效，每次失败后都会重新上传。
                    CheckpointDir = cacheDir,
                };
                if (Progress != null)
                    request.StreamTransferProgress += (data, arg) =>
                    {
                        var _arg = (arg as StreamTransferProgressArgs);
                        var percent = (int)(1.0F * _arg.TransferredBytes / _arg.TotalBytes * 100);
                        Progress.Invoke(percent);
                    };
                // 断点续传上传。
                var resp = _client.ResumableUploadObject(request);
                requestId = resp.RequestId;
                return resp.HttpStatusCode == HttpStatusCode.OK;
            }
            catch (Exception ex)
            {
                return false;
            }
        }
    }
}
